package com.magenta.maxoptra.gis.format.postal;

/**
 * @author Sergey Grachev
 */

import com.magenta.maxoptra.gis.PostalCode;
import com.magenta.maxoptra.gis.format.PostalCodeFormat;
import com.magenta.maxoptra.gis.format.PostalCodeFormatter;
import com.magenta.maxoptra.gis.postal.PostalCodeUK;
import org.apache.commons.lang.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Format for postal code of UK. By default space character and sector + unit parts is optional,
 * use {@link com.magenta.maxoptra.gis.format.PostalCodeFormatter#STRICT} flag for an exact match on all rules
 * <p/>
 * <a href="http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom">See more...<a/>
 *
 * @author Sergey Grachev
 */
public class PostalCodeFormatUK implements PostalCodeFormat {
    /**
     * Space character and sector + unit parts is optional
     */
    protected static final Pattern PATTERN = Pattern.compile("(?:" +
            "(?:([A-Z-[QVX]])([0-9][0-9]?))" + // A9 9AA or A99 9AA
            "|" +
            "(?:([A-Z-[QVX]][A-Z-[IJZ]])([0-9][0-9]?))" + // AA9 9AA or AA99 9AA
            "|" +
            "(?:([A-Z-[QVX‌​]])([0-9][A-HJKSTUW]))" + // A9A 9AA
            "|" +
            "(?:([A-Z-[QVX]][A-Z-[IJZ]])([0-9][ABEHMNPRVWXY]))" + // AA9A 9AA
            ")" +
            "(?:(?:\\s+([0-9])?([A-Z-[C‌​IKMOV]]{2})?)|(?:([0-9])([A-Z-[C‌​IKMOV]]{2}))?)"); // ---- 9AA, ---- 9, ----9AA

    protected static final Pattern PATTERN_STRICT = Pattern.compile("(?:" +
            "(?:([A-Z-[QVX]])([0-9][0-9]?))" + // A9 9AA or A99 9AA
            "|" +
            "(?:([A-Z-[QVX]][A-Z-[IJZ]])([0-9][0-9]?))" + // AA9 9AA or AA99 9AA
            "|" +
            "(?:([A-Z-[QVX‌​]])([0-9][A-HJKSTUW]))" + // A9A 9AA
            "|" +
            "(?:([A-Z-[QVX]][A-Z-[IJZ]])([0-9][ABEHMNPRVWXY]))" + // AA9A 9AA
            ")" +
            "\\s([0-9])([A-Z-[C‌​IKMOV]]{2})"); // --- 9AA

    @Override
    public PostalCode parse(final String postalCode, final int flags) throws ParsePostalCodeException {
        if (StringUtils.isBlank(postalCode)) {
            throw new IllegalArgumentException("Postal code value is null or empty");
        }

        final String code = postalCode.toUpperCase();

        if ("GIR 0AA".equals(code) || "GIR0AA".equals(code)) {
            return new PostalCodeUK("GIR 0AA", "", "", "", true);
        }

        final boolean isStrict = (flags & PostalCodeFormatter.STRICT) != 0;

        final Matcher matcher = (isStrict ? PATTERN_STRICT : PATTERN).matcher(code);
        if (matcher.matches()) {
            String area;
            final String district;
            if ((area = matcher.group(1)) != null) { // A9 9AA or A99 9AA
                district = matcher.group(2);
            } else if ((area = matcher.group(3)) != null) { // AA9 9AA or AA99 9AA
                district = matcher.group(4);
            } else if ((area = matcher.group(5)) != null) { // A9A 9AA
                district = matcher.group(6);
            } else if ((area = matcher.group(7)) != null) { // AA9A 9AA
                district = matcher.group(8);
            } else {
                throw new ParsePostalCodeException(postalCode);
            }

            String sector;
            final String unit;
            if ((sector = matcher.group(9)) != null) {
                unit = matcher.group(10);
            } else if ((sector = matcher.group(11)) != null) {
                unit = matcher.group(12);
            } else {
                unit = null;
            }

            if (isStrict && (sector == null || unit == null)) {
                throw new ParsePostalCodeException(postalCode);
            }

            return new PostalCodeUK(area, district, sector, unit, false);
        }

        throw new ParsePostalCodeException(postalCode);
    }

    @Override
    public PostalCode parse(final String postalCode) throws ParsePostalCodeException {
        return parse(postalCode, 0);
    }

    @Override
    public String format(final PostalCode postalCode) {
        return postalCode.toString();
    }
}

